package com.yvan.leto;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.yvan.YvanUtil;
import com.yvan.platform.JsonWapper;
import com.yvan.platform.StringUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.curator.framework.recipes.cache.TreeCacheEvent;

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;

@Slf4j
public class LetoManage {

    static final ConcurrentMap<String, String> CACHE = Maps.newConcurrentMap();

    public static volatile AppConfig appConfig = new AppConfig();
    public static volatile ConfigRegistry configRegistry = new ConfigRegistry();
    public static volatile ConfigApi configApi = new ConfigApi(appConfig.letoServer);

    private static final List<ConfigListener> listeners = Lists.newCopyOnWriteArrayList();
    private static final CopyOnWriteArrayList<String> segements = Lists.newCopyOnWriteArrayList();

    public static JsonWapper getJsonWapper(String segment) {
        return new JsonWapper(CACHE.getOrDefault(segment, "{}"));
    }

    /**
     * 读取配置文件
     */
    public static String getSegmentContent(String segment) {
        enableCheck();
        return CACHE.computeIfAbsent(segment, k -> configApi.getSegmentContent(LetoManage.appConfig.env, LetoManage.appConfig.appId, segment));
    }

    private static void expireSegmentContent(String segment) {
        enableCheck();
        CACHE.remove(segment);
    }

    public static void init() throws InterruptedException {

        if(!appConfig.isEnable())
            return;

        //初始化配置注册中心
        log.info("leto config init ...");
        Boolean isConfigServerOk = configApi.isConfigServerOk();
        if(isConfigServerOk){
            ZkProperties zkProperties = configApi.getZkProperty();

            log.info("leto init from {}...", zkProperties.getServerList());
            configRegistry.init(zkProperties);

            segements.clear();
            segements.addAll(configRegistry.loadSegments(LetoManage.appConfig.env, LetoManage.appConfig.appId));
            if (!configRegistry.checkProjectNodeExists(LetoManage.appConfig.env, LetoManage.appConfig.appId)) {
                Thread.currentThread().interrupt();
                throw new InterruptedException();
            }
            if (!configRegistry.checkSegmentNodeExists(LetoManage.appConfig.env, LetoManage.appConfig.appId, segements)) {
                Thread.currentThread().interrupt();
                throw new InterruptedException();
            }
        }

        //根据配置中心情况准备segments数据
        Boolean isLoadByNative = prepareSegements(isConfigServerOk);

        if(isConfigServerOk){
            //构建 zk 的 leto-client instance
            configRegistry.writeInstance(LetoManage.appConfig.env, LetoManage.appConfig.appId, segements);
        }

        //初始化 segments
        for (String segment : segements) {
            applySegment(segment);
        }

        if(!isLoadByNative && appConfig.isEnableWatchChange()){
            //监听项目节点
            configRegistry.watchProjectChange(LetoManage.appConfig.env, LetoManage.appConfig.appId, new ConfigRegistry.WatchProcess() {
                @Override
                public void interrupt(ConfigRegistry.WatchProcessEvent event) throws InterruptedException {
                    Thread.currentThread().interrupt();
                    throw new InterruptedException();
                }

                @Override
                public void onChange(ConfigRegistry.WatchProcessEvent event) {
                    expireSegmentContent(event.segment);
                    applySegment(event.segmentFullName);
                }
            });
        }
    }

    public static void clearListener() {
        listeners.clear();
    }

    public static void addListener(ConfigListener listener) {
        listeners.add(listener);
        val segmentName = listener.watchSegment();
        if (segements.contains(segmentName)) {
            listener.onChange(new ConfigChangeEvent(segmentName, TreeCacheEvent.Type.INITIALIZED, getSegmentContent(segmentName)));
        }
    }

    @SneakyThrows
    private static Boolean prepareSegements(Boolean isConfigServerOk){

        Map<String,String> remoteSegementsContentMap = Maps.newConcurrentMap();
        Boolean isLoadByNative = true;

        String revId = null;
        if(isConfigServerOk){
            try{
                for (String segment : segements) {
                    val segmentName = SegmentHelper.getSegmentName(segment);
                    remoteSegementsContentMap.put(segmentName, configApi.getSegmentContent(LetoManage.appConfig.env, LetoManage.appConfig.appId,segmentName));
                    //TODO get revId from getSegmentContent
                    revId = "1";
                }
                isLoadByNative = false;
            }catch (Exception ex){
                log.error("leto config server can not prepare all segement,will load by last native file cache",ex);
                CACHE.clear();
            }
        }

        String cacheDirPath = LetoManage.appConfig.cachePath + "/" + LetoManage.appConfig.appId + "/";
        if (!isLoadByNative) {
            if (LetoManage.appConfig.cacheEnable) {
                for (String segment : segements) {
                    val segmentName = SegmentHelper.getSegmentName(segment);
                    val segmentExt = SegmentHelper.getSegmentExt(segment);
                    String filePath = cacheDirPath + segmentName + "." + segmentExt;
                    YvanUtil.saveToFile(filePath, remoteSegementsContentMap.get(segmentName));
                }
            }
            if(StringUtils.isNotEmpty(revId)){
                RevUtils.setRevId(LetoManage.appConfig.env, LetoManage.appConfig.appId,revId);
            }
        }else{
            List<File> lastCacheFiles = Lists.newArrayList();
            YvanUtil.getFileList(cacheDirPath,lastCacheFiles);
            String fileName;
            for(File file:lastCacheFiles){
                fileName = file.getName();
                val segmentName = fileName.substring(0,fileName.lastIndexOf("."));
                val segmentExt = fileName.substring(fileName.lastIndexOf(".") + 1);
                val segment = segmentName + "@" + segmentExt;
                CACHE.put(segmentName, YvanUtil.readFile(file));
                segements.add(segment);
            }
        }

        return isLoadByNative;
    }

    private static void applySegment(String segmentFullName) {

        val segmentName = SegmentHelper.getSegmentName(segmentFullName);

        for (ConfigListener listener : listeners) {
            if (segmentName.equals(listener.watchSegment())) {
                listener.onChange(new ConfigChangeEvent(segmentName, TreeCacheEvent.Type.INITIALIZED, getSegmentContent(segmentName)));
            }
        }
    }

    public static Boolean isEnable(){
        return appConfig.isEnable();
    }

    private static void enableCheck(){
        if(!isEnable())
            throw new RuntimeException("leto is not enable");
    }

}
